using System;
using System.Collections;
using System.Security.Cryptography;
using System.Text;

namespace Quizz.Infrastructure
{
	/// <summary>
	/// Summary description for RandomPasswordGenerator.
	/// </summary>
	public class RandomPasswordGenerator
	{
		private static readonly char[] Letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray();
		private static readonly char[] Numbers = "1234567890".ToCharArray();
		private static readonly char[] Symbols = "!@#$%^&*.?".ToCharArray();

		int _minimumLength, _maximumLength;

	    string[] _characterTypes;

		enum CharacterType
		{
			Uppercase,
			Lowercase,
			Special,
			Number
		}

	    public bool IncludeUpper { get; set; }

	    public bool IncludeLower { get; set; }

	    public bool IncludeNumber { get; set; }

	    public bool IncludeSpecial { get; set; }

	    public int MinimumLength
		{
			get
			{
				return _minimumLength;
			}
			set
			{
				if(value > _maximumLength)
				{
					throw new ArgumentOutOfRangeException("MinimumLength must be greater than MaximumLength");
				}
				_minimumLength = value;
			}
		}

		public int MaximumLength
		{
			get
			{
				return _maximumLength;
			}
			set
			{
				if(value < _minimumLength)
				{
					throw new ArgumentOutOfRangeException("MaximumLength must be greater than MinimumLength");
				}
				_maximumLength = value;
			}
		}

		public RandomPasswordGenerator()
		{
			_minimumLength = 6;
			_maximumLength = 20;
			IncludeSpecial = true;
			IncludeNumber = true;
			IncludeUpper = true;
			IncludeLower = true;
		}

		public RandomPasswordGenerator(bool includeSpecial, bool includeNumber, bool includeUpper, bool includeLower) : this()
		{
			IncludeNumber = includeNumber;
			IncludeSpecial = includeSpecial;
			IncludeUpper = includeUpper;
			IncludeLower = includeLower;
		}

		/// <summary>
		/// Randomly creates a password.
		/// </summary>
		/// <returns>A random string of characters.</returns>
		public string Create()
		{
			_characterTypes = GetCharacterTypes();

			var password = new StringBuilder(_maximumLength);
			
			//Get a random length for the password.
			var currentPasswordLength = RandomNumber.Next(_maximumLength);

			//Only allow for passwords greater than or equal to the minimum length.
			if(currentPasswordLength < _minimumLength)
			{
				currentPasswordLength = _minimumLength;
			}

			//Generate the password
			for(var i = 0; i < currentPasswordLength; i++)
			{
				password.Append(GetCharacter());
			}

			return password.ToString();
		}

		/// <summary>
		/// Determines which character types should be used to generate
		/// the current password.
		/// </summary>
		/// <returns>A string[] of character that should be used to generate the current password.</returns>
		private string[] GetCharacterTypes()
		{
			var characterTypes = new ArrayList();
			foreach(var characterType in Enum.GetNames(typeof(CharacterType)))
			{
				var currentType = (CharacterType)Enum.Parse(typeof(CharacterType), characterType, false);
				var addType = false;
				switch(currentType)
				{
					case CharacterType.Lowercase:
						addType = IncludeLower;
						break;
					case CharacterType.Number:
						addType = IncludeNumber;
						break;
					case CharacterType.Special:
						addType = IncludeSpecial;
						break;
					case CharacterType.Uppercase:
						addType = IncludeUpper;
						break;
				}
				if(addType)
				{
					characterTypes.Add(characterType);
				}
			}
			return (string[])characterTypes.ToArray(typeof(string));
		}
		
		/// <summary>
		/// Randomly determines a character type to return from the 
		/// available CharacterType enum.
		/// </summary>
		/// <returns>The string character to append to the password.</returns>
		private string GetCharacter()
		{
			var characterType = _characterTypes[RandomNumber.Next(_characterTypes.Length)];
			var typeToGet = (CharacterType)Enum.Parse(typeof(CharacterType), characterType, false);
			switch(typeToGet)
			{
				case CharacterType.Lowercase:
					return Letters[RandomNumber.Next(Letters.Length)].ToString().ToLower();
				case CharacterType.Uppercase:
					return Letters[RandomNumber.Next(Letters.Length)].ToString().ToUpper();
				case CharacterType.Number:
					return Numbers[RandomNumber.Next(Numbers.Length)].ToString();
				case CharacterType.Special:
					return Symbols[RandomNumber.Next(Symbols.Length)].ToString();
			}
			return null;
		}
	}

    public static class RandomNumber
    {
        private static readonly RNGCryptoServiceProvider Random = new RNGCryptoServiceProvider();
        private static readonly byte[] Bytes = new byte[4];

        public static int Next(int max)
        {
            if (max <= 0)
            {
                throw new ArgumentOutOfRangeException("max");
            }
            Random.GetBytes(Bytes);
            var value = BitConverter.ToInt32(Bytes, 0) % max;
            if (value < 0)
            {
                value = -value;
            }
            return value;
        }
    }
}
